/*
 * kogis.cpp
 *
 * Copyright (C) 2008, 2009
 *
 * Lukasz Bieniasz-Krzywiec
 * Dariusz Leniowski
 *
 * glowny plik wykonywalny
 */

#include "kogis.h"

/*
 * ZMIENNE GLOBALNE:
 */

int       N;
int       M;
int       TIME;
int       RODZAJOW;
int       *RODZAJE;
int				RODZAJE_REF[MAX_RODZAJOW+1];

int       RANDOM;
int       FWWK;
int       REPUTACJA;
int       FITNESS;
int       SELEKCJA;
real      PRAWD_BLEDU;
int       GRAF;

int       ILE_WIERZCHOLKOW;
int       ILE_SASIADOW;

int       RODZAJ_SYMULACJI;

int       time;

real      gra[2][2];

osobnik   **populacja;

int       *osobnik_fitness;
int       *fitness_osobnik;

int       rodz[MAX_RODZAJOW+1];

rnd       *r;
fwwk      *f;
fitness   *fit;
selekcja  *sel;
graf      *grf;

real      *tablica_wyplat;


/*
 * FUNKCJE:
 */

void wczytaj_parametry(char *plik) {
  FILE *fin = fopen(plik, "r");
  fscanf(fin, "%*s");
  fscanf(fin, "%lf%lf%lf%lf", &gra[0][0], &gra[0][1], &gra[1][0], &gra[1][1]);

  fscanf(fin, "%*s");
  fscanf(fin, "%d%*s%d%*s%d%*s", &N, &M, &TIME);
  ASSERT(N > 0 && M > 0 && TIME > 0);

  fscanf(fin, "%*s");
  fscanf(fin, "%d%*s", &RODZAJOW); ASSERT(RODZAJOW != 0);

  RODZAJE = new int[RODZAJOW];

  for (int i = 0; i < RODZAJOW; ++i) {
    fscanf(fin, "%d", &RODZAJE[i]);
    RODZAJE_REF[RODZAJE[i]] = i;
    ASSERT(RODZAJE[i] != 0);
  }
  fscanf(fin, "%*s");

  fscanf(fin, "%*s");
  fscanf(fin, "%d%*s", &RANDOM); ASSERT(RANDOM != 0);
  fscanf(fin, "%d%*s", &FWWK); ASSERT(FWWK != 0);
  fscanf(fin, "%d%*s", &REPUTACJA); ASSERT(REPUTACJA != 0);
  fscanf(fin, "%d%*s", &FITNESS); ASSERT(FITNESS != 0);
  fscanf(fin, "%d%*s", &SELEKCJA); ASSERT(SELEKCJA != 0);
  fscanf(fin, REALF"%*s", &PRAWD_BLEDU);
  fscanf(fin, "%d%*s", &GRAF); ASSERT(SELEKCJA != 0);

  fscanf(fin, "%*s");

  fscanf(fin, "%d%*s", &ILE_WIERZCHOLKOW); ASSERT(ILE_WIERZCHOLKOW != 0);
  fscanf(fin, "%d%*s", &ILE_SASIADOW); ASSERT(ILE_SASIADOW != 0);
  fscanf(fin, "%d%*s", &RODZAJ_SYMULACJI); ASSERT(RODZAJ_SYMULACJI != 0);

  fclose(fin);

  printf("-----\n");

  printf("RANDOM %d\n", RANDOM);
  printf("FWWK %d\n", FWWK);
  printf("REPUTACJA %d\n", REPUTACJA);
  printf("FITNESS %d\n", FITNESS);
  printf("SELEKCJA %d\n", SELEKCJA);
  printf("PRAWD BLEDU "REALF"\n", PRAWD_BLEDU);
  printf("GRAF %d\n", GRAF);

  printf("-----\n");

  printf("ILE_WIERZCHOLKOW %d\n", ILE_WIERZCHOLKOW);
  printf("ILE_SASIADOW %d\n", ILE_SASIADOW);
  printf("RODZAJ_SYM %d\n", RODZAJ_SYMULACJI);

  printf("-----\n");

  printf("RODZAJOW = %d\n", RODZAJOW);

  for (int i = 0; i < RODZAJOW; ++i) {
    printf("RODZAJE[%d] = %d\n", i, RODZAJE[i]);
  }

  printf("\n");
}

void grajcie(int wierzcholek, int sasiad) {
  //  0 znaczy zdrada(D)
  //  1 znaczy pelna kooperacja (C)
  // -2 znaczy TFT

  real p = populacja[wierzcholek]->strategia;
  real q = populacja[sasiad]->strategia;

  if (p == -2.0 && q == 0.0) {
	  p = populacja[sasiad]->utworzony + 1 >= time ? 1.0 : 0.0;
  } else if (p == 0.0 && q == -2.0) {
	  q = populacja[wierzcholek]->utworzony + 1 >= time ? 1.0 : 0.0;
  } 
  if (p == -2.0) p = 1.0;
  if (q == -2.0) q = 1.0;

  ASSERT(0.0 <= p && p <= 1.0);
  ASSERT(0.0 <= q && q <= 1.0);

  switch (RODZAJ_SYMULACJI) {
    case LOSOWANIE:
      // Zamiast losowac wartosc bool z prawdopodobienstwiem p (q), wystarczy nasycic p (q).
      if(p > r->rand()) p = 1.0; else p = 0.0;
      if(q > r->rand()) q = 1.0; else q = 0.0;
      break;

    case WARTOSC_OCZEKIWANA:
      /* tu juz nic nie trzeb robic */
      break;
  }
  real wyplata_a =   p *   q  * gra[0][0] +
                  (1-p)*   q  * gra[1][0] +
                     p *(1-q) * gra[0][1] +
                  (1-p)*(1-q) * gra[1][1];
  real wyplata_b =   p *   q  * gra[0][0] +
                  (1-p)*   q  * gra[0][1] +
                     p *(1-q) * gra[1][0] +
                  (1-p)*(1-q) * gra[1][1];

  ASSERT(wyplata_a >= 0.0);
  ASSERT(wyplata_b >= 0.0);

  // policz wartosc krawedzi na ktorej gramy

  real krawedz = f->oblicz(wierzcholek, sasiad);
  
  ASSERT(0.0 <= krawedz && krawedz <= 1.0);

  tablica_wyplat[sasiad + N*wierzcholek] = wyplata_a*krawedz;
  tablica_wyplat[wierzcholek + N*sasiad] = wyplata_b*krawedz;
}

int wez_losowego_sasiada(int wierzcholek) {
  int sasiad, wybrany;
  real p;

  p = 0.0; wybrany = -1;
  FOREN(sasiad) {
    real krawedz = f->oblicz(wierzcholek, sasiad);
    if (krawedz != 0.0) {
      real q = r->rand();
      if (q > p) {
        p = q;
        wybrany = sasiad;
      }
    }
  }

  return wybrany;
}

/*
 * GLOWNY PROGRAM:
 */

int main(int argc, char **argv) {
  int   wierzcholek;
  int   sasiad;
  real  suma;
  FILE  *fout = NULL;

  if (argc != 2 && argc != 3) {
    printf("Sposob uzycia:\n");
    printf("\tkogis parametry.inp [wyniki]\n");
    exit(1);
  }

  wczytaj_parametry(argv[1]);

  if (argc == 3) {
    fout = fopen(argv[2], "w");
  }

  // INICJALIZACJA
  time = 0;

  fprintf(stderr,"tworzenie tablic...\n");
  populacja = new osobnik*[N];
  osobnik_fitness = new int[N];
  fitness_osobnik = new int[N];
  tablica_wyplat = new real[N*N];

  for (int i = 0; i <= MAX_RODZAJOW; ++i) rodz[i] = 0;

  fprintf(stderr,"tworzenie generatora liczb losowych...\n");

  switch (RANDOM) {
    case RANDOM_CPP:
      r = new random_parallel_cpp;
      break;
    default:
      blad("wybrano nieznany generator liczb losowych!\n");
      break;
  }

  fprintf(stderr,"inicjalizacja generatora liczb losowych...\n");

  r->init();

  fprintf(stderr,"tworzenie fwwk...\n");
  switch (FWWK) {
    case FWWK_MIN:
      f = new fwwk_min;
      break;
    case FWWK_MAX:
      f = new fwwk_max;
      break;
    case FWWK_MEAN:
      f = new fwwk_mean;
      break;
    default:
      blad("wybrano nieznana FWWK!\n");
      break;
  }

  fprintf(stderr,"tworzenie przystosowania...\n");

  switch (FITNESS) {
    case FITNESS_STALY:
      fit = new fitness;
      break;
    case FITNESS_OSTATNIA_WYPLATA:
      fit = new fitness_ostatnia_wyplata;
      break;
    case FITNESS_OSTATNIA_WYPLATA_ZNORMALIZOWANA:
      fit = new fitness_ostatnia_wyplata_znormalizowana;
      break;
    case FITNESS_LICZEBNOSC_RODZAJU:
      fit = new fitness_liczebnosc_rodzaju;
      break;
    case FITNESS_WYPLATA_RODZAJU:
      fit = new fitness_wyplata_rodzaju;
      break;
    case FITNESS_WYPLATA_ZNORMALIZOWANA_RODZAJU:
      fit = new fitness_wyplata_znormalizowana_rodzaju;
      break;
    default:
      blad("wybrano nieznany fitness!\n");
      break;
  }

  fprintf(stderr,"tworzenie selekcji...\n");

  switch (SELEKCJA) {
    case SELEKCJA_PROSTA:
      sel = new selekcja_prosta(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA:
      sel = new selekcja_imitacja(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_WSZYSTKIE:
      sel = new selekcja_imitacja_wszystkie(PRAWD_BLEDU);
      break;
    case SELEKCJA_PROCES_MORANA:
      sel = new selekcja_proces_morana();
      break;
    case SELEKCJA_PROCES_MORANA_ARTYKUL:
      sel = new selekcja_proces_morana_artykul(PRAWD_BLEDU);
      break;
    case SELEKCJA_PROCES_MORANA_WSZYSTKIE_ARTYKUL:
      sel = new selekcja_proces_morana_wszystkie_artykul(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW:
      sel = new selekcja_imitacja_rodzajow(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW_WSZYSTKIE:
      sel = new selekcja_imitacja_rodzajow_wszystkie(PRAWD_BLEDU);
      break;
    case SELEKCJA_PROCES_MORANA_DAREK:
      sel = new selekcja_proces_morana_darek(PRAWD_BLEDU);
      break;
    case SELEKCJA_PROCES_MORANA_WSZYSTKIE_DAREK:
      sel = new selekcja_proces_morana_wszystkie_darek(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW2:
      sel = new selekcja_imitacja_rodzajow2(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW2_WSZYSTKIE:
      sel = new selekcja_imitacja_rodzajow2_wszystkie(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW3:
      sel = new selekcja_imitacja_rodzajow3(PRAWD_BLEDU);
      break;
    case SELEKCJA_IMITACJA_RODZAJOW3_WSZYSTKIE:
      sel = new selekcja_imitacja_rodzajow3_wszystkie(PRAWD_BLEDU);
      break;
default:
      blad("wybrano nieznana selekcje!\n");
      break;
  }

  fprintf(stderr,"inicjalizacja grafu...\n");

#define szerokosc 20

  switch (GRAF) {
	  case GRAF_PELNY:
      grf = new graf_pelny();
      break;
    case GRAF_PIERSCIEN:
      grf = new graf_pierscien();
      break;
    case GRAF_KRATA4:
      grf = new graf_krata4<szerokosc>();
      break;
    case GRAF_KRATA6:
      grf = new graf_krata6<szerokosc>();
      break;
    case GRAF_KRATA8:
      grf = new graf_krata8<szerokosc>();
      break;
    case GRAF_KRATA_3D:
      grf = new graf_krata_3D<5,5>();
      break;
    default:
      blad("wybrano nieznany graf!\n");
      break;
  }

  if (fout != NULL) {
    grf->rozmiar(fout);
  }

  fprintf(stderr,"inicjalizacja osobnikow...\n");

  // Inicjalizuj populacje po rowno wszystkimi ustawionymi rodzajami.
  // Nie dopisywać parallel !!!
  FOREN(wierzcholek) fabryka::nowy(RODZAJE[r->rand(RODZAJOW)], wierzcholek);
  //FOREN(wierzcholek) fabryka::nowy(RODZAJE[wierzcholek%50<20], wierzcholek);

  /* Rozkład początkowy: */
  for(int ii = 0; ii < RODZAJOW; ++ii) {
    printf("# %s = %d, ", osobnik::nazwa(RODZAJE[ii]), rodz[RODZAJE[ii]]);
  }
  printf("\n");

  fprintf(stderr,"start symulacji...\n");

  // GLOWNA PETLA
  for(; time < TIME; time ++)
  {
    // ROZPOCZNIJ EPOKE

    #pragma omp parallel for private(wierzcholek)
    for(wierzcholek = 0; wierzcholek < N; ++ wierzcholek) populacja[wierzcholek]->rozpoczecie_epoki();

    #pragma omp parallel for private(wierzcholek)
    FOREN(wierzcholek) {
    	int x; real stopien = 0.0;
    	FOREN(x) {
      	stopien += f->oblicz(wierzcholek, x);
    	}
    	populacja[wierzcholek]->stopien = stopien;
  	}

    // PRZEPROWADZ SYMULACJE

    #pragma omp parallel for private(wierzcholek)
    FOREN(wierzcholek) populacja[wierzcholek]->nowa_strategia();

    #pragma omp parallel for private(wierzcholek)
    FOREN(wierzcholek) populacja[wierzcholek]->wyplata = 0;

    switch (ILE_WIERZCHOLKOW) {
      case WSZYSTKIE_WIERZCHOLKI:
        #pragma omp parallel for private(wierzcholek, sasiad)
        FOREN(wierzcholek) {
          FOREN(sasiad) tablica_wyplat[wierzcholek + N*sasiad] = 0;
        }

        switch (ILE_SASIADOW) {
          case WSZYSCY_SASIEDZI:
            #pragma omp parallel for private(wierzcholek, sasiad)
            FOREN(wierzcholek) {
              for (sasiad = wierzcholek + 1; sasiad < N; ++ sasiad) {
                grajcie(wierzcholek, sasiad);
              }
            }
            break;

          case JEDEN_SASIAD:
            #pragma omp parallel for private(wierzcholek, sasiad)
            FOREN(wierzcholek) {
              if ((sasiad = wez_losowego_sasiada(wierzcholek)) != -1) {
                grajcie(wierzcholek, sasiad);
              }
            }
            break;
        }

        #pragma omp parallel for private(wierzcholek, sasiad)
        FOREN(wierzcholek) {
          populacja[wierzcholek]->wyplata = 0;
          FOREN(sasiad) {
            populacja[wierzcholek]->wyplata += tablica_wyplat[sasiad + N*wierzcholek];
          }
          populacja[wierzcholek]->reputac->przelicz();
        }
        break;

      case JEDEN_WIERZCHOLEK:
        wierzcholek = r->rand(N);
        switch (ILE_SASIADOW) {
          case WSZYSCY_SASIEDZI:
            int wierzcholek2;
            #pragma omp parallel for private(wierzcholek2, sasiad)
            FOREN(wierzcholek2) {
              FOREN(sasiad) tablica_wyplat[wierzcholek2 + N*sasiad] = 0;
            }

            #pragma omp parallel for private(sasiad)
            FOREN(sasiad) {
              if (sasiad != wierzcholek) grajcie(wierzcholek, sasiad);
            }
            #pragma omp parallel for private(sasiad)
            FOREN(sasiad) {
              populacja[sasiad]->wyplata = tablica_wyplat[wierzcholek + N*sasiad];
            }
            suma = 0.0;
            #pragma omp parallel for private(sasiad) reduction(+:suma)
            FOREN(sasiad) {
              suma += tablica_wyplat[sasiad + N*wierzcholek];
            }
            populacja[wierzcholek]->wyplata = suma;
            break;

          case JEDEN_SASIAD:
            if ((sasiad = wez_losowego_sasiada(wierzcholek)) != -1) {
              grajcie(wierzcholek, sasiad);
            }
            populacja[wierzcholek]->wyplata = tablica_wyplat[sasiad + N*wierzcholek];
            populacja[sasiad]->wyplata = tablica_wyplat[wierzcholek + N*sasiad];
            break;
        }

        populacja[wierzcholek]->reputac->przelicz();
        break;
    }

    // POLICZ FITNESS
    fit->oblicz();

    // WYKONAJ SELEJCKE NATURALNA
    sel->wykonaj();

    // ZAKONCZ EPOKE
    #pragma omp parallel for private(wierzcholek)
    FOREN(wierzcholek) populacja[wierzcholek]->zakonczenie_epoki();

    // WYPISZ WYNIKI
    if (time + 1 == TIME) {
      FOREN(wierzcholek) {
        populacja[wierzcholek]->wypisz(stdout);
        fprintf(stdout,"\n");
      }
    }

    printf("max fitness: " REALF " by %s, min fitness: " REALF " by %s \n",
            populacja[fitness_osobnik[0]]->fitness, osobnik::nazwa(populacja[fitness_osobnik[0]]->rodzaj),
            populacja[fitness_osobnik[N-1]]->fitness, osobnik::nazwa(populacja[fitness_osobnik[N-1]]->rodzaj));

	int ile_niez = 0;

    // Wypisz ile osobnikow mamy ktorego rodzaju
    for(int ii = 0; ii < RODZAJOW; ++ii) {
      printf("# %s = %d, ", osobnik::nazwa(RODZAJE[ii]), rodz[RODZAJE[ii]]);
     	if (rodz[RODZAJE[ii]] > 0) ile_niez += 1;
    }
    printf("\n");

    if (fout != NULL && (SELEKCJA % 2 == 0 || time % N == 0)) {
      grf->wypisz(fout);
    }

    if (TIME > 20 && time % (TIME/20) == 0) {
      fprintf(stderr, "Zakonczono epoke %d.\n", time);
    }

    if (ile_niez == 1 && PRAWD_BLEDU == 0.0) break;
  }

  // KONIEC

  fprintf(stderr, "usuwanie selekcji...\n"); delete sel;
  fprintf(stderr, "usuwanie fwwk...\n"); delete f;
  fprintf(stderr, "usuwanie przystosowania...\n"); delete fit;
  fprintf(stderr, "usuwanie generatora liczb losowych...\n"); delete r;
  fprintf(stderr, "usuwanie tablic...\n");

  delete populacja;
  delete osobnik_fitness;
  delete fitness_osobnik;
  delete tablica_wyplat;

  if (argc == 3) {
    fclose(fout);
  }

  fprintf(stderr, "koniec\n");
  return 0;
}
